package mapper

import (
	"bytes"
	"encoding/json"
	"errors"
	"github.com/elastic/go-elasticsearch/v7"
	"github.com/elastic/go-elasticsearch/v7/esapi"
	"github.com/zeromicro/go-zero/core/jsonx"
	"github.com/zeromicro/go-zero/core/logx"
	tools "go-zero-project/common/utils"
	"go-zero-project/services/project/rpc/internal/mapper/entity"
	queryBO "go-zero-project/services/project/rpc/internal/mapper/entity/bo"
	"io"
	"reflect"
	"strings"
)

const defaultPageNum = 1
const defaultPagePageSize = 20

type ProjectMapper struct {
	client *elasticsearch.Client
}

func NewProjectMapper(c *elasticsearch.Client) *ProjectMapper {
	return &ProjectMapper{
		client: c,
	}
}

// SearchPageList https://blog.51cto.com/u_15483641/6085842
func (m *ProjectMapper) SearchPageList(bo queryBO.ProjectBO) (*queryBO.PageProjectBO[entity.ProjectEntity], error) {
	projectEntity := entity.NewProjectEntity()
	// 组装查询条件
	toMapForReflect, toMapErr := tools.BeanToMapForReflect(bo)
	if toMapErr != nil {
		return nil, toMapErr
	}
	mustArr := m.queryCondition(toMapForReflect)
	// 组装排序
	orders := bo.Orders
	sortArr := make([]interface{}, 0)
	// 打分字段
	sortArr = append(sortArr, map[string]interface{}{
		"_score": map[string]interface{}{
			"order": "desc",
		},
	})
	if len(orders) > 0 {
		for _, v := range orders {
			orderStr := "desc"
			if v.Order == 1 {
				orderStr = "asc"
			}
			sortArr = append(sortArr, map[string]interface{}{
				v.FieldName: map[string]interface{}{
					"order": orderStr,
				},
			})
		}
	}
	queryMap := make(map[string]interface{})
	if len(mustArr) <= 0 {
		queryMap["match_all"] = map[string]interface{}{}
	} else {
		queryMap["bool"] = map[string]interface{}{
			"must": mustArr,
		}
	}
	query := map[string]interface{}{
		// 查询条件组装
		"query": queryMap,
		// 高亮显示条件封装
		"highlight": map[string]interface{}{
			"pre_tags":            []string{"<Zwx>"},
			"post_tags":           []string{"</Zwx>"},
			"require_field_match": false,
			"fields": map[string]interface{}{
				"name": map[string]interface{}{},
			},
		},
		// 排序信息
		"sort": sortArr,
	}
	// 序列化
	marshal, err := jsonx.Marshal(query)
	logx.Infof("查询条件：%s", string(marshal))
	if err != nil {
		return nil, err
	}
	// 设置分页
	pageNum := bo.PageNum
	pageSize := bo.PageSize
	if pageSize <= 0 {
		pageSize = defaultPagePageSize
	}
	if pageNum <= 0 {
		pageNum = defaultPageNum
	}
	pageFrom := (pageNum - 1) * pageSize
	from := int(pageFrom)
	size := int(pageSize)
	response, err := m.client.Search(func(request *esapi.SearchRequest) {
		request.Index = []string{projectEntity.IndexName()}
		request.TrackTotalHits = true
		request.From = &from
		request.Size = &size
		request.Pretty = true
		request.SourceIncludes = projectEntity.FieldNames()
		request.Body = bytes.NewReader(marshal)
	})
	if err != nil {
		return nil, err
	}
	// 关闭操作流
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(response.Body)

	if response.IsError() {
		var e map[string]interface{}
		if err := json.NewDecoder(response.Body).Decode(&e); err != nil {
			logx.Errorf("Error parsing the response body: %s", err)
			return nil, err
		} else {
			// Print the response status and error information.
			logx.Errorf("[%s] %s: %s",
				response.Status(),
				e["error"].(map[string]interface{})["type"],
				e["error"].(map[string]interface{})["reason"],
			)
			return nil, errors.New(tools.BeanToJson(e["error"].(map[string]interface{})["reason"]))
		}
	}
	var result map[string]interface{}
	if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
		logx.Errorf("Error parsing the response body: %s", err)
	}
	// Print the response status, number of results, and request duration.
	total := int(result["hits"].(map[string]interface{})["total"].(map[string]interface{})["value"].(float64))
	logx.Infof(
		"[%s] %d hits; took: %dms",
		response.Status(),
		total,
		int(result["took"].(float64)),
	)
	resp := queryBO.PageProjectBO[entity.ProjectEntity]{
		PageNum:   int(pageNum),
		PageSize:  int(pageSize),
		TotalPage: total,
	}
	var recordArr []*entity.ProjectEntity
	// Print the ID and document source for each hit.
	for _, hit := range result["hits"].(map[string]interface{})["hits"].([]interface{}) {
		hitMap := hit.(map[string]interface{})
		id := hitMap["_id"]
		source := hitMap["_source"]
		// process  highlight
		highlight := hitMap["highlight"]
		sourceMap := source.(map[string]interface{})
		if highlight != nil {
			highlightMap := highlight.(map[string]interface{})
			if len(highlightMap) > 0 {
				for k := range highlightMap {
					sourceMap[k] = highlightMap[k].([]interface{})[0]
				}
			}
		}
		info := &entity.ProjectEntity{}
		info.DocId = id.(string)
		tools.JsonToBean(tools.BeanToJson(sourceMap), info)
		recordArr = append(recordArr, info)
	}
	resp.Records = recordArr
	return &resp, nil
}

func (m *ProjectMapper) queryCondition(toMapForReflect map[string]interface{}) []interface{} {
	mustArr := make([]interface{}, 0)
	for k, v := range toMapForReflect {
		// skip field
		if v == nil || k == "pageNum" || k == "pageSize" || k == "orders" {
			continue
		}
		termMap := make(map[string]interface{})
		// K == keyword
		if k == "keyword" {
			if len(v.(string)) > 0 {
				termMap["match"] = map[string]interface{}{
					"name": v,
				}
			}
		} else if k == "roofAreaRange" {
			roofAreaRangeTypeArr := v.([]int32)
			if len(roofAreaRangeTypeArr) <= 0 {
				continue
			}
			for _, t := range roofAreaRangeTypeArr {
				switch t {
				case 1:
					termMap["range"] = map[string]interface{}{
						"totalArea": map[string]interface{}{
							"from":          3000,
							"to":            10000,
							"include_lower": true,
							"include_upper": false,
							"boost":         1,
						},
					}
				case 2:
					termMap["range"] = map[string]interface{}{
						"totalArea": map[string]interface{}{
							"from":          10000,
							"to":            20000,
							"include_lower": true,
							"include_upper": false,
							"boost":         1,
						},
					}
				case 3:
					termMap["range"] = map[string]interface{}{
						"totalArea": map[string]interface{}{
							"from":          20000,
							"include_lower": true,
							"include_upper": false,
							"boost":         1,
						},
					}
				default:
					logx.Infof("skip roofAreaRange type:%s", v)
				}
			}

		} else {
			// process terms
			arrBool := strings.Contains(reflect.TypeOf(v).String(), "[]")
			if arrBool {
				if reflect.TypeOf(v).String() == "[]int32" {
					valArr := v.([]int32)
					if len(valArr) > 0 {
						termMap["terms"] = map[string]interface{}{
							k:       v,
							"boost": 1,
						}
					}
				}
				if reflect.TypeOf(v).String() == "[]int64" {
					valArr := v.([]int64)
					if len(valArr) > 0 {
						termMap["terms"] = map[string]interface{}{
							k:       v,
							"boost": 1,
						}
					}
				}

			} else {
				if reflect.TypeOf(v).String() == "int64" {
					// process terms
					if v.(int64) <= 0 {
						continue
					}
				}
				if reflect.TypeOf(v).String() == "int32" {
					// process terms
					if v.(int32) <= 0 {
						continue
					}
				}
				termMap["term"] = map[string]interface{}{
					k: map[string]interface{}{
						"value": v,
						"boost": 1,
					},
				}
			}
		}
		if len(termMap) <= 0 {
			continue
		}
		mustArr = append(mustArr, termMap)
	}
	return mustArr
}
